import java.util.Iterator;
import java.util.HashMap;
import java.util.Random;
import java.util.TreeSet;

public class RovingEye3 {
	
	TestData[] data;
	double[][][] training;
	NeuralNet net;
	NeuralNet mut;
	
	public RovingEye3() {
		data = data();
		training = training();
		
		String[][] layout = {{"B", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L"},
									{"S", "S", "S"}, {"S", "S", "S"}, 
									{"SC", "SC", "SC", "SC", "SC", "SC", "SC", "SC", "SC"}};
		net = new NeuralNet(NeuralNet.quickCPPN(layout), 1);
		mut = new NeuralNet(NeuralNet.quickCPPN(layout), 1);
	}
	
	public static void main(String[] args) {
		RovingEye3 eye = new RovingEye3();
		double d = Double.POSITIVE_INFINITY;
		Random r = new Random();
		while (d > 0.3) {
			for (int i = 0; i < eye.net.size(); i++) {
				for (int j = 0; j < eye.net.size(i); j++) {
					Node n = eye.net.getNode(i, j);
					for (int k = 0; k < n.size(); k++) {
						double w = n.getWeight(k);
						eye.mut.getNode(i, j).setWeight(k, w + r.nextGaussian());
					}
				}
			}
			double d1 = 0;
			int s = 21;
			for (int i = 0; i < eye.data.length; i+=s) {
	    		double[][] frames = eye.data[i].fr;
	            int[] c = new int[2];
	            double m = Double.NEGATIVE_INFINITY;
	    		for (int j = 0; j < frames.length; j++) {
	    			eye.mut.processInput(frames[j]);
	                double[] v = eye.mut.getOutputs();
	                for (int k = 0; k < v.length; k++) {
	                    if (v[k] > m) {
	                        m = v[k];
	                        c[0] = j/9 + k/3;
	                        c[1] = j%9 + k%3;
	                    }
	                }
	    		}
	    		d1 += Math.sqrt(Math.pow(eye.data[i].cc[0]-c[0],2) + Math.pow(eye.data[i].cc[1]-c[1],2));
			}
			d1 /= 9072/s;
			if (d1 < d) {
				NeuralNet temp = eye.net;
				eye.net = eye.mut;
				eye.mut = temp;
				d = d1;
				System.out.println(d);
			}
			
		}
		eye.evaluate();
	}
	
	public TestData[] data() {
		TestData[] r = new TestData[9072];
		int k = 0;
		for (int i = 1; i < 10; i++) {
			for (int j = 1; j < 10; j++) {
				for (int n = 0; n < 11; n++) {
					for (int m = 0; m < 11; m++) {
						//Input
						boolean f = true;
						for (int a = -1; a < 2; a++) {
							for (int b = -1; b < 2; b++) {
								if (i+a == n && j+b == m) {
									f = false;
								}
							}
						}
						if (f) {
							r[k] = new TestData(i-1,j-1,n,m);
							k++;
							if (k > 9071) {
								return r;
							}
						}
					}
				}
			}
		}
		return r;
	}
	
	public double[][][] training() {
		int frac = 1134;//3024;
		//Count number of training examples
		int j = 112;
		for (int i = 0; i < data.length/frac; i++) {
			for (int l = 0; l < data[i*frac].fr.length; l++) {
				double tot = 0;
				for (int k = 0; k < data[i*frac].fr[l].length; k++) {
					if (k < 9) {
						tot += data[i*frac].fr[l][k];
					}
				}
				if (tot > 0) {
					j++;
				}
			}
			j++;
		}
		
		training = new double[j][3][];
		j = 0;
		for (int i = 0; i < data.length/frac; i++) {
			for (int l = 0; l < data[i*frac].fr.length; l++) {
				training[j][0] = new double[data[i*frac].fr[l].length];
				//Sum values to check if square is filled
				double tot = 0;
				for (int k = 0; k < data[i*frac].fr[l].length; k++) {
					training[j][0][k] = data[i*frac].fr[l][k];
					if (k < 9) {
						tot += data[i*frac].fr[l][k];
					}
				}
				training[j][1] = new double[9];
				if (tot >= 9) {
					training[j][1][(int) (data[i*frac].or[0]*3+data[i*frac].or[1])] = 1;
				}
				training[j][2] = new double[9];
				for (int k = 0; k < 9; k++) {
					training[j][2][k] = k;
				}
				if (tot > 0) {
					j++;
				}
			}
			training[j][0] = new double[data[i*frac].fr[0].length];
			training[j][1] = new double[9];
			training[j][2] = new double[9];
			for (int k = 0; k < 9; k++) {
				training[j][2][k] = k;
			}
			j++;
		}
		for (int i = 0; i < data.length; i+=81) {
			for (int l = 0; l < data[i].fr.length; l++) {
				training[j][0] = new double[data[i].fr[l].length];
				//Sum values to check if square is filled
				double tot = 0;
				for (int k = 0; k < data[i].fr[l].length; k++) {
					training[j][0][k] = data[i].fr[l][k];
					if (k < 9) {
						tot += data[i].fr[l][k];
					}
				}
				training[j][2] = new double[9];
				for (int k = 0; k < 9; k++) {
					training[j][2][k] = k;
				}
				training[j][1] = new double[9];
				if (tot >= 9) {
					training[j][1][(int) (data[i].or[0]*3+data[i].or[1])] = 1;
					j++;
				}
			}
		}
		return training;
	}
	
	public void evaluate() {
        HashMap<Double, Integer> store = new HashMap<Double, Integer>();
        HashMap<Integer, Integer> res = new HashMap<Integer, Integer>();
        double tot = 0;
    	for (int i = 0; i < data.length; i++) {
    		double[][] frames = data[i].fr;
            int[] c = new int[2];
            double m = Double.NEGATIVE_INFINITY;
    		for (int j = 0; j < frames.length; j++) {
    			net.processInput(frames[j]);
                double[] v = net.getOutputs();
                for (int k = 0; k < v.length; k++) {
                    if (v[k] > m) {
                        m = v[k];
                        c[0] = j/9 + k/3;
                        c[1] = j%9 + k%3;
                    }
                }
    		}
            double d = Math.sqrt(Math.pow(data[i].cc[0]-c[0],2) + Math.pow(data[i].cc[1]-c[1],2));
            tot += (d/9072.0);
            if (store.containsKey(d)) {
				int count = store.get(d);
				store.put(d, count+1);
			} else {
				store.put(d, 1);
			}
            if (d == 0) {
            	int l = (int) (data[i].or[0]*3+data[i].or[1]);
            	if (res.containsKey(l)) {
    				int count = res.get(l);
    				res.put(l, count+1);
    			} else {
    				res.put(l, 1);
    			}
            }
    	}
        TreeSet<Double> k = new TreeSet<Double>();
		k.addAll(store.keySet());
		Iterator<Double> iter = k.iterator();
		while(iter.hasNext()) {
			double v = iter.next();
			System.out.println(v+": "+store.get(v));
		}
		System.out.println("Average: "+tot);
		tot = 0;
		TreeSet<Integer> x = new TreeSet<Integer>();
		x.addAll(res.keySet());
		Iterator<Integer> x2 = x.iterator();
		while(x2.hasNext()) {
			int v = x2.next();
			tot += res.get(v);
			System.out.println(v+": "+res.get(v));
		}
		System.out.println("Total: "+tot);
    }
    
    private class TestData {
        
        double[][] im = new double[11][11];
        double[][] fr = new double[81][11];
        double[] fc = new double[2]; //Frame coordinate
        double[] co = new double[2]; //Coordinate offset
        double[] or = new double[2]; //Offset rounded
        double[] cc = new double[2]; //Center coordinate
        
        public TestData(int bx, int by, int sx, int sy) {
        	fc[0] = bx;
            fc[1] = by;
            co[0] = (fc[0]/3.0);
            co[1] = (fc[1]/3.0);
            or[0] = Math.floor(co[0]);
            or[1] = Math.floor(co[1]);
            cc[0] = fc[0] + or[0];
            cc[1] = fc[1] + or[1];
        	
            for (int i = 0; i < 3; i++) {
                for (int j = 0; j < 3; j++) {
                    im[bx+i][by+j] = 1;
                }
            }
            im[sx][sy] = 1;
            
			for (int i = 0; i < 9; i++) {
                for (int j = 0; j < 9; j++) {
                    for (int k = 0; k < 9; k++) {
                        fr[i*9+j][k] = im[i+(k/3)][j+(k%3)];
                    }
                    //Adding coordinates to each input
                    fr[i*9+j][9] = or[0]-1;//((double)i) / 9.0;
                    fr[i*9+j][10] = or[1]-1;//((double)j) / 9.0;
				}
			}
        }
    }
}
